from ops import *
from optparse import OptionParser
from threading import *
from net_controller import *

import sys
import pickle

TIMEOUT = 1
NEXT_TIMEOUT = 4
HEARTBEAT_TIME = 5
LEADER_TIMEOUT = 8
class PaxosServer:
    def __init__(self, proc_id, server_count, options, config_path='config.json'):
        self.netController = NetController(proc_id)
        self.server_count = server_count
        self.proc_id = proc_id
        
        # TEMP
        self.history = []
        self.aHistory = []
        self.pHistory = []

        # FOR CLIENT.
        self.cmsgq = []
        self.cmsg = None

        # FOR SERVER.
        self.delay = options.delay
        self.partPropose = options.partPropose
        self.partAccept = options.partAccept
        self.isLeader = False
        self.leaderNum = -1

        self.electDict = {}


        #HB Timer
        self.t = None

        self.leaderT = Timer(LEADER_TIMEOUT, self.leaderTimeout)
        self.leaderT.daemon = True
        self.leaderT.start()

        self.lifeT = None

        # FOR PAXOS.
        self.n = -1

        self.pn = -1 #previous accepted n
        self.av = None 

        self.top_an = -1 #top previously accepted n
        self.top_av = None
        self.promiseCount = 0

        self.aav = None #accepted-ack value
        self.aas = -1 #accepted-ack slot
        self.ackCount = 0

        self.status = STAT.FREE;

        # FOR PLAYLIST.
        self.playlist = dict()
        self.curCommand = 0
        self.slot = 0 

    def heartBeat(self):
        self.netController.send_all((OPS.LEADERBROAD, ()), True, 0)
        if self.isLeader:
            self.t = Timer(HEARTBEAT_TIME, self.heartBeat)
            self.t.daemon = True
            self.t.start()

    def setLeader(self):
        self.isLeader = True
        self.heartBeat()        

    def send(self, proc_id, msg):
        self.netController.send(proc_id, msg, self.delay)

    def sendToAll(self, msg):
        self.netController.send_all(msg, False, self.delay)

    def leaderTimeout(self):
        if not self.isLeader:
            self.leaderNum = (self.leaderNum + 1) % self.server_count 
            self.send(self.leaderNum, (OPS.ELECT,()))
            self.leaderT = Timer(LEADER_TIMEOUT*2, self.leaderTimeout)
            self.leaderT.daemon = True
            self.leaderT.start()

    def next(self):
        ret = self.netController.next(True, NEXT_TIMEOUT)

        if ret == None:
            return

        print ret
        from_proc, msg = ret
        msg_type, args = msg

        if msg_type == OPS.LEADERBROAD:
            self.isLeader = False
            self.electDict = {}
            if self.leaderT:
                self.leaderT.cancel()
            self.leaderT = Timer(LEADER_TIMEOUT, self.leaderTimeout)
            self.leaderT.daemon = True
            self.leaderT.start()
            
        if msg_type == OPS.ELECT:
            self.electDict[from_proc] = 1
            if len(self.electDict.keys()) >= ((self.server_count/2) + 1):
                self.electDict = {}
                self.leaderNum = self.proc_id
                self.setLeader()
                self.slot = self.curCommand
                self.commandNum = 0
                self.n = self.proc_id



        # If the message is from a client, put it on the client q-tip (backwards).
        if self.isLeader:
            if msg_type < OPS.CLIENT:
                print "queuing", len(self.cmsgq)
                self.cmsgq.append(ret)


        if msg_type == OPS.PREPARE:
            print "OPS.PREP", from_proc, "recieved", msg
            slot = args[0]
            new_n = args[1]
            

            if slot < len(self.aHistory) and self.aHistory[slot]:
                pn, av = self.aHistory[slot]
            else:
                pn = -1
                av = None    
                
            if self.pn == -1:
                while len(self.pHistory) <= slot:
                    self.pHistory.append(None)
                self.pHistory[slot] = new_n 
                pickle
                self.send(from_proc, (OPS.PROMISE, (self.slot, True, None, None)))

            elif new_n > pn and new_n > self.pHistory[slot]:
                self.pHistory[slot] = new_n
                self.send(from_proc, (OPS.PROMISE, (self.slot, True, pn, av)))

            else:
                self.send(from_proc, (OPS.PROMISE, (self.slot, False, pn, av))) 
                

        elif msg_type == OPS.PROMISE and self.isLeader: 
            print "OPS.PROMISE", from_proc, "recieved", msg
            promised = args[1]
            last_an = args[2]
            last_av = args[3]

            if promised:
                self.promiseCount += 1
                if last_av != None:
                    if last_an > self.top_an:
                        self.top_an = last_an
                        self.top_av = last_av
            else:
                new_n = (((last_an/100)+1)*100) + self.proc_id
                self.n = new_n
                self.sendToAll((OPS.PREPARE, (self.slot, self.n)))
                self.promiseCount = 0
                self.top_an = -1
                self.top_av = None

            if self.promiseCount >= ((self.server_count/2) + 1):
                self.promiseCount = 0 # wont ever have two majority sets so 
                                      # will only send_all OPS.ACCEPT ONCE
                sendv = None
                if self.top_av != None:
                    sendv = self.top_av
                    if not self.partAccept:
                        self.sendToAll((OPS.ACCEPT, (self.slot, self.n, sendv)))
                    else:
                        start, i = self.partAccept
                        for x in xrange(start, start+i):
                            self.send(x % self.server_count, (OPS.ACCEPT, (self.slot, self.n, sendv)))
                        self.t.cancel()
                        sys.exit(1)

                    self.aav = sendv
                    self.ackCount = 0
                    self.aas = self.slot

                else:
                    self.status = STAT.WAITING

        elif msg_type == OPS.ACCEPT:
            print "OPS.ACCEPT", from_proc, "recieved", msg

            slot = args[0]
            while len(self.aHistory) <= slot:
                self.aHistory.append(None)

            an = args[1]
            av = args[2]

            if self.pHistory[slot] <= an: 
                self.aHistory[slot] = (an, av)
                self.send(from_proc, (OPS.ACCEPTACK, (slot, av)))

        elif msg_type == OPS.ACCEPTACK and self.isLeader: 
            print "OPS.ACCEPTACK", from_proc, "recieved", msg
            aav = args[1]
            aas = args[0]

            self.ackCount += 1
            
            if self.ackCount >= ((self.server_count/2) + 1):
                self.ackCount = 0 #wont send learn twice
                self.sendToAll((OPS.LEARN, (self.slot, aav)))

        elif msg_type == OPS.LEARN:
            print "OPS.LEARN", from_proc, "recieved", msg
            slot = args[0]
            command = args[1]
            while len(self.history) <= slot:
                self.history.append(None)
            self.history[slot] = command
            print "slot", slot, "\nvalue", command

            #args[1] := (from_proc, (cmsg_type, (key, url))), or something like it
            cmsg_type = args[1][1][0]
            cmsg_args = args[1][1][1]
            cret_msg = None

            # Process client messages.
            if self.curCommand == slot:
                if cmsg_type == OPS.ADD:
                    c_key = cmsg_args[1]
                    c_url = cmsg_args[2]
                    self.playlist[c_key] = c_url
                    cret_msg = args[1][1]

                elif cmsg_type == OPS.DEL:
                    c_key = cmsg_args[1]
                    #self.playlist.pop(c_key, "")
                    if c_key in self.playlist:
                        del self.playlist[c_key]
                    cret_msg = args[1][1]

                elif cmsg_type == OPS.EDT:
                    c_key = cmsg_args[1]
                    c_url = cmsg_args[2]
                    if c_key in self.playlist:
                        self.playlist[c_key] = c_url
                    cret_msg = args[1][1]

                elif cmsg_type == OPS.PRT:
                    cret_msg = (cmsg_type, self.playlist)

                self.curCommand += 1

            elif self.isLeader:
                temppl = {}
                for x in xrange(0, slot+1):
                    intr = self.history[x]
                    ocmsg_type = intr[1][1][0]
                    ocmsg_args = intr[1][1][1]
                    if ocmsg_type == OPS.ADD:
                        c_key = ocmsg_args[1]
                        c_url = ocmsg_args[2]
                        temppl[c_key] = c_url
                        cret_msg = intr[1][1]

                    if ocmsg_type == OPS.DEL:
                        c_key = ocmsg_args[1]
                        if c_key in temppl:
                            del temppl[c_key] 
                        cret_msg = intr[1][1]

                    if ocmsg_type == OPS.EDT:
                        c_key = ocmsg_args[1]
                        c_url = ocmsg_args[2]
                        if c_key in temppl:
                            temppl[c_key] = c_url
                        cret_msg = intr[1][1]
                    
                    if ocmsg_type == OP.PRT:
                        cret_msg = (ocmsg_type, temppl)
                        
                        
            if self.isLeader:
                self.slot = self.curCommand
                self.status = STAT.FREE

                self.send(args[1][0], cret_msg)
                self.send(self.proc_id, (OPS.FLUSH,()))
    
    def prepareCurrentSlot(self):
        self.status = STAT.WORK
        if not self.partPropose:
            self.sendToAll((OPS.PREPARE,(self.slot, self.n)))
            self.promiseCount = 0
        else:
            start, i = self.partPropose
            for x in xrange(start, start+i):
                self.send(x % self.server_count, (OPS.PREPARE, (self.slot, self.n)))

    def proposeNextCommand(self):
        if self.status == STAT.WAITING and self.cmsgq:
            self.cmsg = self.cmsgq.pop(0)
            self.status = STAT.WORK
            sendv = self.cmsg

            if self.cmsg in self.history:
                index = self.history.index(self.cmsg)
                self.sendToAll((OPS.LEARN, (index, self.cmsg)))
                
            else:
                if not self.partAccept:
                    self.sendToAll((OPS.ACCEPT, (self.slot, self.n, sendv)))
                else:
                    start, i = self.partAccept
                    for x in xrange(start, start+i):
                        self.send(x % self.server_count, (OPS.ACCEPT, (self.slot, self.n, sendv)))
                    sys.exit(1)

                self.ackCount = 0
        

    def run(self):
        while True:
            self.next()
            if self.status == STAT.FREE and self.isLeader:
                self.prepareCurrentSlot()
            elif self.status == STAT.WAITING and self.isLeader:
                self.proposeNextCommand()


if __name__ == "__main__":

    usage = "usage: %prog [options] proc_id num_servers"
    parser = OptionParser(usage=usage)

    parser.add_option("-d", "--delay", default=0, dest="delay",
                      help="set message delay", metavar="<n>", type="int")

    parser.add_option("-P", "--partPropose", dest="partPropose",
                      help="set partial propose message broadcast on leader election", metavar="<start> <num>", nargs=2, type="int")

    parser.add_option("-A", "--partAccept", dest="partAccept",
                      help="set partial accept message broadcast on leader election", metavar="<start> <num>", nargs=2, type="int")

    (options, args) = parser.parse_args()

    if len(args) < 2:
        parser.print_help()
        sys.exit(1)

    print "delay =       ", options.delay
    print "partPropose = ", options.partPropose
    print "partAccept =  ", options.partAccept
    print "proc_id =     ", args[0]
    print "num_servers = ", args[1]

    process = PaxosServer(int(args[0]), int(args[1]), options)

    process.run()
    print "SERVER.PY DONE."
